package org.truenewx.tnxjee.core.enums;

import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;

import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.util.Assert;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.boot.ApplicationContextRunner;
import org.truenewx.tnxjee.core.caption.CaptionUtil;
import org.truenewx.tnxjee.core.enums.annotation.EnumConstantScene;
import org.truenewx.tnxjee.core.enums.annotation.EnumSubTypeCaption;
import org.truenewx.tnxjee.core.enums.annotation.EnumTypeCaptionPattern;
import org.truenewx.tnxjee.core.enums.annotation.SimpleEnumType;
import org.truenewx.tnxjee.core.spec.BooleanEnum;
import org.truenewx.tnxjee.core.spec.EnumGrouped;
import org.truenewx.tnxjee.core.spec.Mutability;
import org.truenewx.tnxjee.core.util.*;

/**
 * 枚举字典工厂（解析器实现）
 *
 * @author jianglei
 */
@Configuration("enumDictResolver")
@ConfigurationProperties("tnxjee.enums.dict")
public class EnumDictResolverImpl implements EnumDictResolver, ApplicationContextRunner {

    @Autowired
    private ApplicationContext context;
    @Autowired(required = false)
    private EnumDictCreator enumDictCreator = EnumDictImpl::new;
    private Map<Locale, EnumDict> dicts = new Hashtable<>();
    private List<EnumTypeParser> enumTypeParsers = new ArrayList<>();
    private boolean initial = true;
    private Set<String> basePackageNames = new HashSet<>();
    private List<EnumItemResolver<?>> itemResolvers = new ArrayList<>();

    public EnumDictResolverImpl() {
        this.itemResolvers.add(new EnumConstantItemResolver());
        this.itemResolvers.add(new EnumerableItemResolver());
        // EnumConstantItemResolver的排序早于EnumerableItemResolver，默认情况下无需重新排序
    }

    public void setInitial(boolean initial) {
        this.initial = initial;
    }

    public void setBasePackageNames(Set<String> basePackageNames) {
        this.basePackageNames = basePackageNames;
    }

    @Override
    public void addBasePackageName(String basePackageName) {
        this.basePackageNames.add(basePackageName);
    }

    @Override
    public void addItemResolver(EnumItemResolver<?> itemResolver) {
        // 避免重复添加
        if (!this.itemResolvers.contains(itemResolver)) {
            this.itemResolvers.add(itemResolver);
            // 添加自定义枚举项解决器之后，重新排序
            this.itemResolvers.sort(Comparator.comparing(EnumItemResolver::getOrder));
        }
    }

    @Override
    public int getOrder() {
        return CONTEXT_INITIALIZE_ORDER;
    }

    @Override
    public void run(ApplicationContext context) throws Exception {
        // 容器启动后再加载枚举类型解析器，以避免循环依赖
        this.enumTypeParsers.addAll(context.getBeansOfType(EnumTypeParser.class).values());

        if (this.initial) {
            initialize(null);
        }
        // 从Spring容器中找出所有EnumType，加入对应区域的枚举字典中
        Map<String, EnumType> enumTypeMap = context.getBeansOfType(EnumType.class);
        for (Entry<String, EnumType> entry : enumTypeMap.entrySet()) {
            Locale locale = getLocale(entry.getKey());
            EnumDict dict = getEnumDict(locale);
            dict.addType(entry.getValue());
        }
    }

    @Override
    public EnumDict initialize(Locale locale, String... basePackageNames) {
        EnumDict dict = getEnumDict(locale);
        Locale dictLocale = dict.getLocale(); // 一定不为null
        Set<String> basePackageSet = new HashSet<>(this.basePackageNames);
        CollectionUtil.addAll(basePackageSet, basePackageNames);
        for (String basePackageName : basePackageSet) {
            if (StringUtils.isNotBlank(basePackageName) && !dict.isInitialized(basePackageName)) {
                ClassUtil.loopClass(basePackageName, clazz -> {
                    if (clazz.isEnum() || EnumerableItem.class.isAssignableFrom(clazz)) {
                        EnumType enumType = buildEnumType(clazz, null, dictLocale);
                        dict.addType(enumType);

                        Set<String> subTypes = new HashSet<>();
                        ClassUtil.loopStaticPublicField(clazz, field -> {
                            EnumConstantScene[] enumConstantScenes = field.getAnnotationsByType(
                                    EnumConstantScene.class);
                            for (EnumConstantScene enumConstantScene : enumConstantScenes) {
                                String subType = enumConstantScene.subType();
                                if (subTypes.add(subType)) {
                                    EnumType enumSubType = buildEnumType(clazz, subType, dictLocale);
                                    dict.addType(enumSubType);
                                }
                            }
                        });

                        EnumSubTypeCaption[] enumSubTypeCaptions = clazz.getAnnotationsByType(
                                EnumSubTypeCaption.class);
                        for (EnumSubTypeCaption enumSubTypeCaption : enumSubTypeCaptions) {
                            if (LocaleUtil.matches(enumSubTypeCaption.locale(), dictLocale)) {
                                EnumType enumSubType = buildEnumType(clazz, enumSubTypeCaption.subname(),
                                        dictLocale);
                                enumSubType.setCaption(enumSubTypeCaption.caption());
                                dict.addType(enumSubType);
                            }
                        }
                    }
                });
                dict.setInitialized(basePackageName);
            }
        }
        for (EnumTypeParser enumTypeParser : this.enumTypeParsers) {
            String parserClassName = enumTypeParser.getClass().getName();
            if (!dict.isInitialized(parserClassName)) {
                enumTypeParser.forEach(locale, dict::addType);
                dict.setInitialized(parserClassName);
            }
        }
        return dict;
    }

    @Override
    public EnumDict getEnumDict(Locale locale) {
        if (locale == null) {
            locale = LocaleContextHolder.getLocale();
        }
        if (!Locale.getDefault().equals(locale)) {
            LogUtil.warn(getClass(), "=== locale={}", locale);
        }
        EnumDict dict = this.dicts.get(locale);
        if (dict == null) { // 没有相应区域的字典就创建一个
            dict = this.enumDictCreator.create(locale);
            this.dicts.put(locale, dict);
        }
        return dict;
    }

    @Override
    public EnumType getEnumType(String type, String subType, Locale locale) {
        EnumDict dict = getEnumDict(locale);
        EnumType result = dict.getType(type, subType);
        if (result == null) { // 枚举字典里没有该枚举类型，则尝试构建枚举类型
            result = buildEnumType(type, subType, locale);
            // 如果无法用指定区域创建枚举类型，则使用默认区域创建
            if (result == null && !Locale.getDefault().equals(locale)) {
                result = buildEnumType(type, subType, Locale.getDefault());
            }
            // 如果无法构建，则创建空的枚举类型，以避免缓存穿透
            if (result == null) {
                result = new EnumType(type, subType, Strings.EMPTY, Mutability.NONE);
            }
            dict.addType(result);
        }
        return result;
    }

    @Override
    public EnumItem getEnumItem(Enum<?> enumConstant, Locale locale) {
        Class<?> enumClass = enumConstant.getClass();
        String typeName = getEnumTypeName(enumClass);
        return getEnumItem(typeName, null, enumConstant.name(), locale);
    }

    private String getEnumTypeName(Class<?> clazz) {
        String typeName = null;
        SimpleEnumType simpleEnumType = clazz.getAnnotation(SimpleEnumType.class);
        if (simpleEnumType != null) {
            String simpleTypeName = simpleEnumType.name();
            if (StringUtils.isBlank(simpleTypeName)) {
                typeName = clazz.getSimpleName();
            } else {
                Assert.isTrue(!StringUtil.isNumbers(simpleTypeName), "The type name cannot be all number");
                typeName = simpleTypeName;
            }
        }
        if (StringUtils.isBlank(typeName)) {
            typeName = clazz.getName();
        }
        return typeName;
    }

    @Override
    public EnumItem getEnumItem(String type, String subType, String key, Locale locale, String... keys) {
        EnumType enumType = getEnumType(type, subType, locale);
        if (enumType != null) { // 尝试构建不成功时item可能为null
            return enumType.getItem(key, keys);
        }
        return null;
    }

    @Override
    public EnumItem getEnumItemByCaption(String type, String subType, String caption, Locale locale) {
        EnumType enumType = getEnumType(type, subType, locale);
        if (enumType != null) {
            return enumType.getItemByCaption(caption);
        }
        return null;
    }

    @Override
    public EnumItem getEnumItemByValue(String type, String subType, String value, Locale locale) {
        EnumType enumType = getEnumType(type, subType, locale);
        if (enumType != null) {
            return enumType.getItemByValue(value);
        }
        return null;
    }


    @Override
    public String getValue(String type, String subType, String key, String... keys) {
        EnumItem item = getEnumItem(type, subType, key, null, keys);
        return item == null ? null : item.getValue();
    }

    @Override
    public String getText(String type, String subType, String key, Locale locale, String... keys) {
        EnumItem item = getEnumItem(type, subType, key, locale, keys);
        return item == null ? null : item.getCaption();
    }

    @Override
    public String getText(String type, String key, Locale locale, String... keys) {
        return getText(type, null, key, locale, keys);
    }

    @Override
    public String getText(Enum<?> enumConstant, String subType, Locale locale) {
        Class<?> enumClass = enumConstant.getClass();
        String typeName = getEnumTypeName(enumClass);
        return getText(typeName, subType, enumConstant.name(), locale);
    }

    @Override
    public String getText(Enum<?> enumConstant, Locale locale) {
        return getText(enumConstant, null, locale);
    }

    /**
     * 构建指定枚举类型，包括其所有子类型
     *
     * @param type    枚举类型名称
     * @param subType 子枚举类型名称
     * @param locale  地区
     * @return 枚举类型
     */
    private EnumType buildEnumType(String type, String subType, Locale locale) {
        if (locale == null) {
            locale = LocaleContextHolder.getLocale();
        }
        // 优先从配置文件中解析
        EnumType enumType = parseEnumType(locale, type, subType);
        if (enumType == null) {
            // 再尝试作为枚举类型构建
            Class<?> clazz = getClass(type);
            if (clazz != null) {
                enumType = buildEnumType(clazz, subType, locale);
            }
        }
        return enumType;
    }

    private Class<?> getClass(String type) {
        if (BOOLEAN_ENUM_TYPE.equalsIgnoreCase(type)) {
            return BooleanEnum.class;
        } else {
            ClassLoader classLoader = this.context.getClassLoader();
            if (classLoader != null) {
                try {
                    return classLoader.loadClass(type);
                } catch (ClassNotFoundException e) {
                    if (this.basePackageNames != null) {
                        for (String basePackageName : this.basePackageNames) {
                            Holder<Class<?>> classHolder = new Holder<>();
                            ClassUtil.loopClass(basePackageName, c -> {
                                if (type.equals(getEnumTypeName(c))) {
                                    classHolder.set(c);
                                    return false;
                                }
                                return true;
                            });
                            Class<?> clazz = classHolder.get();
                            if (clazz != null) {
                                return clazz;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private EnumType parseEnumType(Locale locale, String type, String subType) {
        for (EnumTypeParser enumTypeParser : this.enumTypeParsers) {
            EnumType enumType = enumTypeParser.parse(locale, type, subType);
            if (enumType != null) {
                return enumType;
            }
        }
        return null;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private EnumType buildEnumType(Class<?> clazz, String subType, Locale locale) {
        EnumType enumType = newEnumType(clazz, subType, locale);

        Enum<?> group = null;
        if (EnumGrouped.class.isAssignableFrom(clazz) && subType != null) {
            Class<Enum> groupEnumClass = ClassUtil.getActualGenericType(clazz, EnumGrouped.class, 0);
            group = EnumUtils.getEnum(groupEnumClass, subType);
            subType = null; // 子类型名称匹配了分组枚举，则置空，以避免重复校验子类型
        }

        List<Field> fields = ClassUtil.getStaticPublicFields(clazz);
        for (int i = 0; i < fields.size(); i++) {
            Field field = fields.get(i);
            if (StringUtils.isBlank(subType) || getConstantScene(subType, field) != null) {
                try {
                    Object fieldValue = field.get(null);
                    if (matchesGroup(fieldValue, group)) {
                        Integer ordinal = getOrdinal(field, subType);
                        if (ordinal != null) {
                            if (ordinal < 0) {
                                if (fieldValue instanceof Enum) {
                                    ordinal = ((Enum<?>) fieldValue).ordinal();
                                } else {
                                    ordinal = i;
                                }
                            }

                            String key = null;
                            String value = null;
                            String caption = null;
                            Map<String, Object> attachment = null;
                            for (EnumItemResolver itemResolver : this.itemResolvers) {
                                Class<?> itemSourceType = ClassUtil.getActualGenericType(itemResolver.getClass(),
                                        EnumItemResolver.class, 0);
                                if (itemSourceType.isAssignableFrom(field.getType())) {
                                    key = itemResolver.getKey(fieldValue);
                                    if (key != null) {
                                        value = itemResolver.getValue(fieldValue);
                                        caption = itemResolver.getCaption(subType, field, fieldValue, locale);
                                        attachment = itemResolver.getAttachment(fieldValue);
                                        break;
                                    }
                                }
                            }

                            if (key != null) {
                                if (caption == null) {
                                    caption = getItemCaption(clazz, subType, field, key, locale);
                                }
                                // 从枚举常量构建的枚举选项可禁用但不可删除
                                EnumItem item = new EnumItem(ordinal, key, caption, Mutability.DISABLE);
                                if (StringUtils.isBlank(value)) {
                                    value = item.getKey();
                                }
                                item.setValue(value);
                                if (attachment != null) {
                                    attachment.forEach(item::addAttachedField);
                                }
                                enumType.addItem(item);
                            }
                        }
                    }
                } catch (IllegalAccessException ignored) {
                }
            }
        }
        return enumType;
    }

    private boolean matchesGroup(Object value, Enum<?> group) {
        if (group == null) {
            return true;
        }
        if (value instanceof EnumGrouped) {
            return ((EnumGrouped<?>) value).group() == group;
        }
        return false;
    }

    private Integer getOrdinal(Field field, String subType) {
        if (field == null) {
            return null;
        }
        EnumConstantScene enumConstantScene = getConstantScene(subType, field);
        if (enumConstantScene != null) {
            return enumConstantScene.ordinal();
        }
        return EnumConstantScene.DEFAULT_ORDINAL;
    }

    private EnumConstantScene getConstantScene(String subType, Field field) {
        if (StringUtils.isNotBlank(subType)) {
            EnumConstantScene[] enumConstantScenes = field.getAnnotationsByType(EnumConstantScene.class);
            for (EnumConstantScene enumConstantScene : enumConstantScenes) {
                if (enumConstantScene.subType().equals(subType)) {
                    return enumConstantScene;
                }
            }
        }
        return null;
    }

    private String getItemCaption(Class<?> clazz, String subType, Field field, String key, Locale locale) {
        String caption = null;
        EnumConstantScene enumConstantScene = getConstantScene(subType, field);
        if (enumConstantScene != null) {
            caption = enumConstantScene.caption();
        }
        if (StringUtils.isBlank(caption)) {
            caption = CaptionUtil.getCaption(field, locale);
        }
        if (StringUtils.isBlank(caption)) { // 默认用key作为显示名称
            caption = key;
        } else {
            caption = roundTypeCaption(clazz, caption);
        }
        return caption;
    }

    /**
     * 创建枚举类型对象，不含枚举项目
     *
     * @param clazz   来源类型
     * @param subname 子类型名称
     * @param locale  区域
     * @return 不含枚举项目的枚举类型对象
     */
    private EnumType newEnumType(Class<?> clazz, String subname, Locale locale) {
        String typeCaption = CaptionUtil.getCaption(clazz, locale);
        // 默认使用枚举类型简称为显示名称
        if (StringUtils.isBlank(typeCaption)) {
            typeCaption = clazz.getSimpleName();
        }
        if (StringUtils.isNotBlank(subname)) {
            typeCaption += Strings.DOT + subname;
        }
        String typeName = getEnumTypeName(clazz);
        return new EnumType(typeName, subname, typeCaption, Mutability.BASIC); // 从枚举类构建的枚举类型只能改动基础信息
    }

    private String roundTypeCaption(Class<?> clazz, String caption) {
        caption = caption.trim();
        EnumTypeCaptionPattern enumTypeCaptionPattern = clazz.getAnnotation(EnumTypeCaptionPattern.class);
        if (enumTypeCaptionPattern != null) {
            String captionPrefix = enumTypeCaptionPattern.prefix();
            if (StringUtils.isNotBlank(captionPrefix) && !caption.startsWith(captionPrefix)) {
                caption = captionPrefix + caption;
            }
            String captionSuffix = enumTypeCaptionPattern.suffix();
            if (StringUtils.isNotBlank(captionSuffix) && !caption.endsWith(captionSuffix)) {
                caption += captionSuffix;
            }
        }
        return caption;
    }

    /**
     * 从资源信息中确定区域
     *
     * @param resourceName 资源名
     * @return 区域
     */
    private Locale getLocale(String resourceName) {
        // 根据资源名称确定区域，要求资源名称以类似_zh_CN样式结尾，且有其它开头
        String[] names = resourceName.split(Strings.UNDERLINE);
        int length = names.length;
        if (length > 2) { // 含有至少两个下划线，含有语言和国别
            return new Locale(names[length - 2], names[length - 1]);
        } else if (length == 2) { // 含一个下划线，含有语言
            return new Locale(names[1]);
        } else { // 不含下划线，则返回null
            return null;
        }
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public <E extends Enum<E>> E getEnumConstantByCaption(Class<E> enumClass, String caption, String groupCaption,
            Locale locale) {
        if (StringUtils.isNotBlank(caption)) {
            String subType = null;
            if (EnumGrouped.class.isAssignableFrom(enumClass)) {
                Class<Enum> groupClass = ClassUtil.getActualGenericType(enumClass, EnumGrouped.class, 0);
                Enum<?> group = getEnumConstantByCaption(groupClass, groupCaption, null, locale);
                if (group != null) {
                    subType = group.name();
                }
            }
            caption = roundTypeCaption(enumClass, caption);
            EnumItem enumItem = getEnumItemByCaption(enumClass.getName(), subType, caption, locale);
            if (enumItem != null) {
                return EnumUtils.getEnum(enumClass, enumItem.getKey());
            }
            // 默认场景找不到，则在所有子类型中查找
            EnumSubTypeCaption[] enumSubTypeCaptions = enumClass.getAnnotationsByType(EnumSubTypeCaption.class);
            for (EnumSubTypeCaption enumSubTypeCaption : enumSubTypeCaptions) {
                enumItem = getEnumItemByCaption(enumClass.getName(), enumSubTypeCaption.subname(), caption, locale);
                if (enumItem != null) {
                    return EnumUtils.getEnum(enumClass, enumItem.getKey());
                }
            }
        }
        return null;
    }

}
